﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Messaging;

namespace Niknak.Services.ServiceCore.Msmq
{
    public sealed class Throttle<ConfigurationSettings, MessageTypeEnum>
        where ConfigurationSettings : IMsmqConfig, new()
        where MessageTypeEnum : struct, IComparable, IConvertible, IFormattable //see http://stackoverflow.com/questions/6438352/using-enum-as-generic-type-parameter-in-c-sharp
    {
        private Dictionary<long, ThrottleItem<MessageTypeEnum>> _dictionary = new Dictionary<long, ThrottleItem<MessageTypeEnum>>();
        private TimeSpan _duration;
        private long _index = 0;
        private Timer _timer;

        public static ConfigurationSettings Settings { get { return new ConfigurationSettings(); } }

        public static readonly Throttle<ConfigurationSettings, MessageTypeEnum> Instance = new Throttle<ConfigurationSettings, MessageTypeEnum>();

        public ThrottleItem<MessageTypeEnum> this[long index]
        {
            get
            {
                if (_dictionary.ContainsKey(index))
                    return _dictionary[index];
                return null;
            }
        }

        private Throttle()
        {
            if (!typeof(MessageTypeEnum).IsEnum)
                throw new Exception("The '" + typeof(MessageTypeEnum).ToString() + "' type must be an Enum");

            _duration = TimeSpan.FromSeconds(Settings.ThrottleDurationSeconds);
            TimerCallback callback = new TimerCallback(CleanThrottle);
            _timer = new Timer(callback, new AutoResetEvent(false), 1000, 1000);
        }

        public long Add(Message message)
        {
            long index;
            lock (typeof(ThrottleItem<MessageTypeEnum>))
            {
                _index++;
                index = _index;

                _dictionary.Add(index, new ThrottleItem<MessageTypeEnum>((MessageTypeEnum)Enum.Parse(typeof(MessageTypeEnum), message.Label, false), message.Id, DateTime.UtcNow + _duration));
            }

            return index;
        }

        public bool Remove(long index)
        {
            if (_dictionary.ContainsKey(index))
                lock (typeof(ThrottleItem<MessageTypeEnum>))
                    if (_dictionary.ContainsKey(index))
                    {
                        _dictionary.Remove(index);
                        return true;
                    }

            return false;
        }

        public bool IsThrottled(MessageTypeEnum messageType)
        {
            long itemCount = 0;

            lock (typeof(ThrottleItem<MessageTypeEnum>))
            {
                Dictionary<long, ThrottleItem<MessageTypeEnum>>.Enumerator en = _dictionary.GetEnumerator();

                while (en.MoveNext())
                {
                    ThrottleItem<MessageTypeEnum> item = en.Current.Value;
                    if (item._MessageType.Equals(messageType))
                        itemCount++;
                }
                en.Dispose();
            }

            return itemCount >= Settings.MaxMessagesHandledPerThrottleDuration;
        }

        private void CleanThrottle(object stateInfo)
        {
            List<long> keyList = new List<long>();

            lock (typeof(ThrottleItem<MessageTypeEnum>))
            {
                Dictionary<long, ThrottleItem<MessageTypeEnum>>.Enumerator en = _dictionary.GetEnumerator();

                while (en.MoveNext())
                {
                    ThrottleItem<MessageTypeEnum> item = en.Current.Value;
                    if (item._Expire < DateTime.UtcNow)
                        keyList.Add(en.Current.Key);
                }
                en.Dispose();
            }

            foreach (long key in keyList)
                Remove(key);
        }
    }
}
